/*
 * Copyright 2004-2006 H2 Group. Licensed under the H2 License, Version 1.0 (http://h2database.com/html/license.html).
 */

// For possible increase in speed! 
// Set g_lcBodyText value later once then do NOT modify its' value after that assignment.
var g_lcBodyText = "",
	g_mainPageLoaded,
	g_FRAMESET_LOADED = "framesetLoaded",
	g_MAIN_PAGE_LOADED = "mainPageLoaded";

function loadFrameset() {
	// <frameset ... onLoad="loadFrameset()"> is overwritten in some browsers by window.onload
	// so input element "framesetLoaded" is a work around
	if (getValueStore(g_FRAMESET_LOADED) == "y") return;
	var a = top.document.location.search.split('&'),
		page = decodeURIComponent(a[0].substr(1)),
		frame = a[1];
	if (page && frame) {
		eval("top." + frame + ".location.href = '" + page + "'");
		setValueStore(g_FRAMESET_LOADED, "y");	// frameset is now loaded
	}
}

function frameMe(frame) {
	if (frame == null) {
		frame = 'main';
	}
	var page = self.document.location.href,
		file = page.substr(page.lastIndexOf('/') + 1);
	file = encodeURIComponent(file);
	// name of the frameset page: old code was "frame.html" now is "index.html"
	if (window.name != frame)
		window.location.href = "index.html?" + file + '&' + frame;
}

/**
 * Get the DOM Object's value from the store using DOM element id.
 *
 * The store is currently html hidden input placed in the left navigation page
 * which is made not to refresh if user press achor links and controls within the website.
 * Nonetheless this javascript page do refreshes, its inclusion and placement is uncertain.
 * Therefore the store act close to a static memory, communication signal/channel
 * except when the page refresh is pressed.
 */
function getValueStore(/*String*/ id) {
	var o = top.menu.document.getElementById(id);
	if (o) return o.value;
	return '';
}

/**
 * Set the DOM Object's value to the store using DOM element id.
 *
 * The store is currently html hidden input placed in the left navigation page
 * which is made not to refresh if user press achor links and controls within the website.
 * Nonetheless this javascript page do refreshes, its inclusion and placement is uncertain.
 * Therefore the store act close to a static memory, communication signal/channel
 * except when the page refresh is pressed.
 */
function setValueStore(/*String*/ id, /*String*/ value) {
	var o = top.menu.document.getElementById(id);
	if (o) o.value = value;
}

function getURLParam(url, strParamName) {
	/* Cater for any URL string */
	var pos = url.indexOf('?'),	// has '?'
		queryStr, i, j;
	if (pos < 0) {
		pos = url.indexOf('&');	// perhaps only '&'
		if (pos < 0) return '';
	}
	queryStr = url.substr(pos +1);
	j = strParamName + '=';
	pos = queryStr.indexOf(j);
	if (pos > -1) {
		pos = pos + j.length;
			// cater for abnormal URL parameter string 
			// e.g. param1=value#toHere&sudoParam=sudoValue
			// search next '&', param value usually ends before the '&' char
		i = queryStr.indexOf('&', pos);
		j = queryStr.indexOf('#', pos);
		// remove any '#' link reference and/or things after '&' char
		if (j > pos && i > j) return decodeURIComponent(queryStr.substring(pos, j));
		if (i < pos) return decodeURIComponent(queryStr.substring(pos));
		if (i > pos) return decodeURIComponent(queryStr.substring(pos, i));
	}
	return '';
}

function highlight() {
	var mainDoc = top.main.document,
		location = mainDoc.location,
		url = location.search;
	if (url.indexOf("?highlight=") < 0) return;
	var body = mainDoc.body, num, 
		word = decodeURIComponent(getURLParam(url, "highlight"));
	body.innerHTML = highlightSearchTerms(body, word, 
		getURLParam(url, "mcase") == "true" ? true : false);
	/* The number behind "#fndWord" has precedence over the number given by the parameter "&fndWord"
	Do NOT reload the page by keeping URL same, "&fndWord" value are NOT changed;
	change only "#fndWord" value */
	if (url.lastIndexOf("#fndWord") > -1) {
		num = parseInt(url.split("#fndWord")[1]);
	} else {
		num = parseInt(getURLParam(url, "fndWord"));
	}
	if (isNaN(num)) {
		num = 0;
	} else {
		num = getValidWordIdNum(num);
	}
	location.href = "#fndWord" + num;
}

/**
 * Is there a valid id with such number?
 * function use in other JavaScript file(s) & html page
 * @return same number if true otherwise 0.
 */
function getValidWordIdNum(num) {
	var loaded = getValueStore(g_MAIN_PAGE_LOADED);
	if (g_lcBodyText == "" || g_mainPageLoaded != loaded) {	// copy page in lower case ONLY once for speed
		g_lcBodyText = top.main.document.body.innerHTML.toLowerCase();
		g_mainPageLoaded = loaded;
		/* Note this is copy/clone string:
		bodyText = top.main.document.body.innerHTML; 
		
		Since pages can be quite large, for speed avoid above if possible use:
		var body = top.main.document.body,
			pos = body.innerHTML.indexOf("...");	// using original body text
		*/
	}
	var doDbl = true, doNon, doSgl, 
		// The id value has "fndWord" string in front and a number at the back.
		// We will use all lowercase for searching purpose
		idStrQuoteDouble = ' id="fndword',	// try with double quote '"'
		idStrQuoteSingle = " id='fndword",	// try with single quote "'"
		idStrQuoteNone = ' id=fndword',	// try with no quote ' '
		pos;
	// browser may change what we have written into body.innerHTML 
	// e.g. Firefox modifies the id value to be inside double quotes
	if (num < 0) {	// grab the end most position with a valid number
		var m = n = g_lcBodyText.length,
			strLen;
		do {
			if (doDbl) {
				pos = findValuePosInsideATag(idStrQuoteDouble, --m);
				if (pos <= 0) {
					doDbl = false;
					doSgl = true;
					m = n;	// reset
				} else
					strLen = idStrQuoteDouble.length;
			}
			if (doSgl) {
				pos = findValuePosInsideATag(idStrQuoteSingle, --m);
				if (pos <= 0) {
					doSgl = false;
					doNon = true;
					m = n;	// reset
				} else
					strLen = idStrQuoteSingle.length;
			}
			if (doNon) {
				pos = findValuePosInsideATag(idStrQuoteNone, --m);
				if (pos <= 0)
					return 0;	// fail!
				else
					strLen = idStrQuoteNone.length;
			}	// after this: pos > 0
			num = parseInt(g_lcBodyText.substring(pos + strLen, pos +21));
			if (isNaN(num)) {
				m = pos;	// grab next available id number
			} else return num;
		} while (m);	// same as (pos > 0) but less the comparison so faster
		return 0;	// should NOT reach here, but just in case
	}	// else (num >= 0)
	
	// make sure we got it on the page
	pos = findValuePosInsideATag(idStrQuoteDouble + num +'" ');	// index at 0 is NOT a correct id
	if (pos <= 0) pos = findValuePosInsideATag(idStrQuoteSingle + num +"' ");
	if (pos <= 0) pos = findValuePosInsideATag(idStrQuoteNone + num +' ');
	if (pos > 0) return num;
	return 0;
}

/**
 * Find a searchValue inside a HTML tag from the back of g_lcBodyText.
 * It must not be 
 * inside a HTML comment tag or
 * inside a <script> block or
 * inside a <noscript> block.
 * <br/>Assume: g_lcBodyText has been updated, searchValue is a non-empty String
 * and if start is given (start >= 0) is true, for speed
 * @param {String}	searchValue	Required. The string to search for
 * @param {Number}	[start]	Optional. The position where to start the search. If omitted, the default value is the length of the string.
 * @return {Number}	the index that is inside a html tag otherwise -1 for failure.
 */
function findValuePosInsideATag(/*String*/ searchValue, /*Number*/ start) {
	// g_lcBodyText, searchValue & (start >= 0) will NOT be tested
	var i;
	if (start == undefined) {
		i = g_lcBodyText.lastIndexOf(searchValue);
	} else {
		i = g_lcBodyText.lastIndexOf(searchValue, start);
	}
	if (0 < i) {
		// skip anything inside a HTML comment tag
		// skip anything inside a <script> block
		// skip anything inside a <noscript> block
		// MUST be inside a HTML tag
		// Allow comparison to be equal since the search result is likely -1 not found
		if (g_lcBodyText.lastIndexOf("<!--", i) <= g_lcBodyText.lastIndexOf("-->", i) 
			&& g_lcBodyText.lastIndexOf("<script", i) <= g_lcBodyText.lastIndexOf("/script>", i) 
			&& g_lcBodyText.lastIndexOf("<noscript", i) <= g_lcBodyText.lastIndexOf("/noscript>", i) 
			&& g_lcBodyText.lastIndexOf(">", i) < g_lcBodyText.lastIndexOf("<", i)) {
			return i;
		}
	}
	return -1;
}

String.prototype.trim=function(cmpChar) {
	if (cmpChar==undefined) {
		cmpChar=' ';
	} else if (cmpChar.length>1) {
		cmpChar=cmpChar.charAt(0);
	}
	var s = this;
	while (s.charAt(0)==cmpChar && s.length>0) {
		s=s.substring(1);
	}
	while (s.charAt(s.length-1)==cmpChar && s.length>0) {
		s=s.substring(0, s.length-1);
	}
	return s;
};

// Search term object with properties
function shTermObj() {
	this.num;
	this.posStart;
	this.posEnd;
	return this;
}

function highlightSearchTerms(body, searchText, matchCase) {
	if (body==undefined || body.innerHTML==undefined || body.innerHTML.length <= 0) {
		return "";
	}
	var loaded = getValueStore(g_MAIN_PAGE_LOADED),
		matchColor = ["ffff00","00ffff","00ff00","ff8080","ee82ee","33ccff","ffa500","ff0080"],
		mcbodyText;
	if (g_lcBodyText == "" || g_mainPageLoaded != loaded) {
		// copy page in lower case ONLY once for speed
		g_lcBodyText = body.innerHTML.toLowerCase();
		g_mainPageLoaded = loaded;
	}
	if (matchCase) {
		mcbodyText = body.innerHTML;
	} else {
		searchText = searchText.toLowerCase();
		mcbodyText = g_lcBodyText;
	}
	var cnt = matchColor.length,
		i, prev, s, shNum = 0,
		shTerm = searchText.trim(',').split(','),
		shTermColor = [],
		shTermPos = [];
	searchText = [];
	// for speed, rebuild search term, remove
	// 1. any "" empty string or undefined variable
	// 2. duplicated search terms
	// fast reverse loop, each loop removed (s < shTerm.length) comparison
	for (s = shTerm.length; s--;) {
		var text = shTerm[s];
		if (text == "" || text == undefined) continue;
		prev = true;
		// fast reverse loop, each loop removed (i < searchText.length) comparison
		for (i = searchText.length; i--;) {
			// include & allow any search term that is part of other search term(s)
			// e.g. "kn", "nowe", "eth" and "weth" is part of knoweth"
			if (text == searchText[i]) {	// found exact duplicate or repeated text
				prev = false;
				break;
			}
		}
		if (prev) {
			var n = findText(mcbodyText, text, 0);	// search from beginning
			if (n >= 0) {	// search term exist
				i = searchText.length;
				// initialize & determine which search term should be done 1st
				searchText[i] = text;	// searchText.length will +1 after this
				shTermColor[i] = matchColor[i % cnt];	// do this once for speed
				shTermPos[i] = n;
				if (i != shNum && (n < shTermPos[shNum] || shTermPos[shNum] < 0)) {
					shNum = i;
				}
			}
		}
	}
	shTerm = searchText;
	i = shTermPos[shNum];
	cnt = prev = 0;
	// IE 8+ & other browser += concatenation is much faster then using array buffer & Array.join('') method
	// array buffer & Array.join('') is much faster in IE 7, IE 6 or below
	var html = '';
	while (i >= 0) {
		//======= handle overlap highlight (searched) term
		// current leading search term: shTerm (array) index is "shNum"
		// its start position is "i", end position is "next"
		var cEnd, cStart,
			hlTerms = [],
			c = hlTerms[0] = new shTermObj(),
			next = i + shTerm[shNum].length,	// next char after search term
			q = shTermPos[shNum] = findText(mcbodyText, shTerm[shNum], next);	// next
		c.num = shNum;
		c.posStart = i;
		c.posEnd = next;
		// find exact overlap end range i.e. var "next"
		for (s = shTerm.length; s--;) {
			var p = shTermPos[s];
			// reverse lazy condition, fast filter out highlight c, or shNum index
			if (next >= p && p >= i) {
				var n = p + shTerm[s].length;
				if (n > next) {	// overlap range increased
					next = n;
				}
			}
		}
		//------- find overlap highlight (searched) term, modify & sort them
		// inclusion algorithm:
		// find overlap highlights within "i" and "next" index
		// this algorithm works for "build html algorithm" below
		for (s = shTerm.length; s--;) {
			var p = shTermPos[s];
			// include those term next to each other, although they do not overlap
			// they will share same element id "fndWord"
			if (i <= p && p <= next) {
				var t = shTerm[s];
				c = new shTermObj();
				c.num = s;
				cStart = c.posStart = p;
				// next char after search term
				cEnd = c.posEnd = p + t.length;
				p = shTermPos[s] = findText(mcbodyText, t, cEnd);	// next
				// MUST loop from front to back
				// compare the new highlight term c against hlTerms array from index 0..n
				var added = false,
					cur = hlTerms.length;
				for (var a = 0; a < cur; a++) {
					// for new highlight c, determine to insert or append to hlTerms
					// 		& chop the highlight text as necessary
					var z = hlTerms[a],
						zEnd = z.posEnd,
						zStart = z.posStart;
					if (cStart < zStart) {
						hlTerms.splice(a, 0, c);	// insert c before z
						added = true;
						if (zStart < cEnd && cEnd <= zEnd) {
							cEnd = c.posEnd = zStart;	// chop overlap, left text's back
						}
						break;	// end
					} else if (cStart == zStart) {
						if (cEnd < zEnd) {
							hlTerms.splice(a, 0, c);	// insert c before z
							added = true;
							zStart = z.posStart = cEnd;	// chop overlap, z start index
							break;	// end
						} else if (cEnd > zEnd) {
							cStart = c.posStart = zEnd;	// chop overlap, left text's front
							continue;
						}	// else if (cEnd == zEnd)	c will disappear
						added = true;	// c is thrown out
						break;	// end
					}	// else if (zStart < cStart)
					// Reduce 2 kinds of chop into 1 (for swapped or not swapped conditions)
					// i.e. highlight c did overlap z, front or behind
					// MUST be true now: zStart < cStart
					if (cStart < zEnd && zEnd <= cEnd) {
						zEnd = z.posEnd = cStart;	// chop overlap, left text's back
					}
					// else if (zEnd > cEnd)	condition handle by html algorithm below
					// else if (zEnd < cStart)	continue
				}
				if (!added) hlTerms[cur] = c;	// append
			}	// if
			// choose next search
			if (p >= 0 && (q < 0 || (p < q))) {
				shNum = s;
				q = p;	// shTermPos[shNum] now is shTermPos[s]
			}
		}
		html += body.innerHTML.substring(prev, i);
		prev = next;
		//======= do the highlight
		// build html algorithm:
		searchText = ' id="fndWord'+ cnt++ +'"';
		// previous highlight element:
		// mimic hlTerms[] element of shTermObj() for speed
		var prevHLNum,		// highlight's colour number
			prevHLEnd,		// highlight's end index
			prevHLStart, 	// highlight's start index
			prevHLEndStack = [],	// a stack, of previous highlight's end index
			stackTop = -1;	// stack top
		s = 0;
		do {
			c = hlTerms[s];
			cStart = c.posStart;
			cEnd = c.posEnd;
			if (cEnd - cStart > 0) {
				if (stackTop >= 0) {
					html += "<span"+ searchText +" style='color:#000000; background-color:#"+ 
						shTermColor[prevHLNum] +";'>";
					searchText = "";
					var drawOnce = true;
					do {
						prevHLEnd = prevHLEndStack[stackTop];
						if (prevHLEnd > cStart) {
							// MUST test, condition can happen
							if (prevHLStart > cStart) {	// preserve the drawing start index
								if (cEnd <= prevHLStart) {	// c is NOT in view
									// modify c to disappear
									cStart = cEnd = prevHLStart;
								} else	// modify c start index so this highlight is filtered later
									cStart = prevHLStart;
								break;	// do nothing else
							}
							if (prevHLStart < cStart)
								html += body.innerHTML.substring(prevHLStart, cStart);
							if (prevHLEnd <= cEnd) {	// c covered over prevHL
								html += "</span>";	// close prevHL <span> then pop stack below
								continue;
							}	// prevHLEnd > cEnd	do NOT pop stack
							break;
						} else if (prevHLEnd < cStart) {	// gap between two highlights
							html += body.innerHTML.substring(prevHLStart, prevHLEnd) +"</span>";
							prevHLStart = prevHLEnd;
						} else {	// ==  i.e. next to each other (prevHLEnd == cStart)
							if (drawOnce) {
								html += body.innerHTML.substring(prevHLStart, cStart);
								prevHLStart = cStart;
								drawOnce = false;
							}
							html += "</span>";
						}
					} while (--stackTop >= 0);	// pop stack
				}
				prevHLNum = c.num;
				// push current hlTerms[s].posEnd to stack
				prevHLEndStack[++stackTop] = cEnd;
				prevHLStart = cStart;
			}	// if
		} while (++s < hlTerms.length);
		if (stackTop >= 0) {	// clear rest of stack
			// last term
			html += "<span"+ searchText +" style='color:#000000; background-color:#"+ 
				shTermColor[prevHLNum] +";'>";
			// searchText = "";	// NOT required
			do {
				prevHLEnd = prevHLEndStack[stackTop];
				html += body.innerHTML.substring(prevHLStart, prevHLEnd) +"</span>";
				prevHLStart = prevHLEnd;
			} while (--stackTop >= 0)	// pop stack
		}
		i = q;
	}	// while
	if (i < 0) {
		html += body.innerHTML.substr(prev);	// last
	}
	return html;
}

/**
 * find the next text, starting from index, inside the html and return the 1st index found that is
 * not inside a HTML comment tag or
 * not inside a <script> block or
 * not inside a <noscript> block.
 * Assume: g_lcBodyText has been updated, and (n >= 0) is true for speed
 * @param html the html
 * @param text the text to find
 * @param n start index
 * @return index or -1 if not found.
 */
function findText(/*string*/ html, /*string*/ text, /*integer*/ n) {
	// (g_lcBodyText && n >= 0) will NOT be tested
	do {
		var i = html.indexOf(text, n);
		if (i < 0) return i;
		// skip anything inside a HTML tag (does NOT included comment tag!)
		// skip anything inside a HTML comment tag
		// skip anything inside a <script> block
		// skip anything inside a <noscript> block
		if (g_lcBodyText.lastIndexOf(">", i) < g_lcBodyText.lastIndexOf("<", i)
			|| g_lcBodyText.lastIndexOf("-->", i) < g_lcBodyText.lastIndexOf("<!--", i)
			|| g_lcBodyText.lastIndexOf("/script>", i) < g_lcBodyText.lastIndexOf("<script", i)
			|| g_lcBodyText.lastIndexOf("/noscript>", i) < g_lcBodyText.lastIndexOf("<noscript", i)) {
			// search term is inside a HTML tag, HTML comment tag, <script> or <noscript> block
			n = i + text.length;	// skip this, find next
			continue;
		}
		return i;
	} while (true);
}
